<?php

namespace System\library;

use System\InitSystem;
use System\library\config\Config;
use System\library\config\SqlConfig;
use System\library\pattern\Singleton;
use System\library\logger\IFormater;
use System\library\Debugger;

/**
 * <b>Class</b> :        Logger<br>
 * <b>Fonction</b> :     Logger classe, permet la gestion des logs, d'ajouter des writers...
 * <br>
 * @version              1.0
 * @author               Dread <dreadlokeur@gmail.com>
 * @license              Licence publique générale GNU http://www.gnu.org/licenses/gpl.html
 * @copyright            Copyright 2011 - MidichloriansPHP and contributors
 * @package              System
 * @subpackage           library
 *
 * @see System\library\pattern\Singleton
 * @see \SplSubject
 */
class Logger extends Singleton implements \SplSubject {
    const EMERGENCY = 0;
    const ALERT = 1;
    const CRITICAL = 2;
    const ERROR = 3;
    const WARNING = 4;
    const NOTICE = 5;
    const INFO = 6;
    const DEBUG = 7;

    protected $_writers; // SplObjectStorage
    private $_logs = array();
    private $_groups = array();
    private static $_logFormater = null; //object IFormater
    private static $_logLevel = self::WARNING;
    private static $_logBacktrace = false;

    public static function setLevel($level) {
        if (!is_int($level) || $level < self::EMERGENCY || $level > self::DEBUG)
            throw new \Exception('Log level is invalid');
        self::$_logLevel = $level;
    }

    public static function setLogBacktrace($boolean) {
        if (!is_bool($boolean))
            throw new \Exception('Log backtrace parameter must be a boolean');
        self::$_logBacktrace = $boolean;
    }

    public static function setFormater(IFormater $formater) {
        self::$_logFormater = $formater;
    }

    public static function getLevel() {
        return self::$_logLevel;
    }

    public static function getLogBacktrace() {
        return self::$_logBacktrace;
    }

    public static function getFormater() {
        if (self::$_logFormater === null)
            throw new \Exception('Log formater isnt setted');
        return self::$_logFormater;
    }

    public function attach(\SplObserver $writer) {
        if ($this->_writers->contains($writer))
            throw new \Exception('Writer "' . $writer . '" is already attached');
        $this->_writers->attach($writer);
        return $this;
    }

    public function detach(\SplObserver $writer) {
        if (!$this->_writers->contains($writer))
            throw new \Exception('Writer "' . $writer . '" don\'t exist');
        $this->_writers->detach($writer);
        return $this;
    }

    public function notify($lastLogOnly = false) {
        if ($this->_writers->count()) {
            foreach ($this->_writers as $writer) {
                if ($lastLogOnly)
                    $writer->update($this, true);
                else
                    $writer->update($this);
            }
        }
        // avoid multicall
        if ($lastLogOnly) {
            array_pop($this->_logs);
        }else
            $this->_logs = array();
    }

    public function __destruct() {
        // log performances and informations
        if (InitSystem::isDebug()) {
            $this->addGroup('Debug', 'Performances and informations', true);
            $this->debug('Configuration : ' . count(Config::getAllCons()) . ' constantes and ' . count(Config::getAllVar()) . ' variables was defined', 'Debug');
            $this->debug('Configuration SQL : ' . SqlConfig::countMasters() . ' masters and ' . SqlConfig::countSlaves() . ' slaves servers was register into ' . SqlConfig::countConfig() . ' configurations', 'Debug');
            // Benchmark
            $bench = InitSystem::getBenchmark();
            if ($bench !== null) {
                $this->debug('Page generated in : ' . $bench->stop()->getStats() . ' seconds', 'Debug');
                $this->debug('Memory used  : ' . $bench->stopMemory()->getStatsMemory() . ' octets - Memory allocated : ' . memory_get_peak_usage(true) . ' octets', 'Debug');
            }
        }
        // call writers and write logs
        $this->notify();
    }

    public function getLogs() {
        return $this->_logs;
    }

    public function getGroups() {
        return $this->_groups;
    }

    public function addGroup($name, $label, $onBottom = false) {
        // Check
        if (!is_string($label))
            throw new \Exception('Label of group must be a string');
        if (key_exists((string) $name, $this->_groups))
            return;

        $this->_logs[] = array();
        // On enregistre le group dans la liste des groupes
        $group = new \stdClass();
        $group->label = $label;
        $group->key = count($this->_logs) - 1;
        $group->onBottom = $onBottom;
        $this->_groups[(string) $name] = $group;
    }

    public function debug($message, $groupName = false, $writingImmediately = false) {
        $this->_addLog($message, self::DEBUG, $groupName, $writingImmediately);
        if (self::getLogBacktrace())
            $this->_logBackTrace();
    }

    public function error($message, $groupName = false, $writingImmediately = false) {
        $this->_addLog($message, self::ERROR, $groupName, $writingImmediately);
    }

    //TODO others public function: notice, emerg etc ...

    protected function __construct() {
        $this->_writers = new \SplObjectStorage();
    }

    private function _addLog($message, $level, $groupName = false, $writingImmediately = false) {
        if (($level <= $this->getLevel() || $this->getLevel() == self::DEBUG)) {
            // On construit le log
            $date = new \DateTime('now');
            $log = new \stdClass();
            $log->message = $message;
            $log->level = $level;
            $log->group = $groupName;
            $log->date = $date->format('Y-m-d H:i:s');

            // On l'ajoute aux logs
            if ($groupName)
                $this->_addLogOnGroup($groupName, $log);
            else
                $this->_logs[] = $log;

            if ($writingImmediately)
                $this->notify(true);
        }
    }

    private function _addLogOnGroup($groupName, $log) {
        if ($groupName && !key_exists((string) $groupName, $this->_groups))
            throw new \Exception('The group "' . $groupName . '" don`t exists');
        $this->_logs[$this->_groups[$groupName]->key][] = $log;
        
    }

    private function _logBackTrace() {
        $traceArray = array_reverse(debug_backtrace());
        $size = count($traceArray);
        foreach ($traceArray as $level => &$trace) {
            $log = '';
            if (key_exists('file', $trace) && key_exists('line', $trace))
                $log .= $trace['file'] . ':' . $trace['line'];
            if (key_exists('class', $trace) && key_exists('function', $trace))
                $log .= ( !empty($log) ? ' - ' : '') . $trace['class'] . '::' . $trace['function'];
            $this->_addLog($log, self::INFO);
            if ($level > $size - 3)
                break;
        }
    }

    public static function getLevelName($levelNumber) {
        switch ($levelNumber) {
            case self::EMERGENCY:
                $levelName = 'EMMERGENCY';
                break;
            case self::ALERT:
                $levelName = 'ALERT';
                break;
            case self::CRITICAL:
                $levelName = 'CRITIC';
                break;
            case self::ERROR:
                $levelName = 'ERROR';
                break;
            case self::WARNING:
                $levelName = 'WARNING';
                break;
            case self::NOTICE:
                $levelName = 'NOTICE';
                break;
            case self::INFO:
                $levelName = 'INFO';
                break;
            case self::DEBUG:
                $levelName = 'DEBUG';
                break;
            default:
                $levelName = 'UNDEFINED LEVEL';
                break;
        }
        return $levelName;
    }

}

?>
